﻿# define _CRT_SECURE_NO_WARNINGS 1
//第19讲：数据在内存中的存储
//1. 整数在内存中的存储
//在讲解操作符的时候，我们就讲过了下⾯的内容：
//整数的2进制表⽰⽅法有三种，即原码、反码和补码
//有符号的整数，三种表⽰⽅法均有符号位和数值位两部分，符号位都是⽤0表⽰“正”，⽤1表
//⽰“负”，最⾼位的⼀位是被当做符号位，剩余的都是数值位。
//正整数的原、反、补码都相同。
//负整数的三种表⽰⽅法各不相同。
//原码：直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
//反码：将原码的符号位不变，其他位依次按位取反就可以得到反码。
//补码：反码 + 1就得到补码。
//对于整形来说：数据存放内存中其实存放的是⼆进制的补码。
//在计算机系统中，数值⼀律⽤补码来表⽰和存储。
//原因在于，使⽤补码，可以将符号位和数值域统⼀处理；
//同时，加法和减法也可以统⼀处理（CPU只有加法器）此外，补码与原码相互转换，其运算过程是
//相同的，不需要额外的硬件电路。
//2. ⼤⼩端字节序和字节序判断
//当我们了解了整数在内存中存储后，我们调试看⼀个细节：
//#include <stdio.h>
//int main()
//{
//	int a = 0x11223344;
//
//	return 0;
//}
//内存中存储; 0x000000A42697FA94  44 33 22 11  D3".
//调试的时候，我们可以看到在a中的 0x11223344 这个数字是按照字节为单位，倒着存储的。这是为
//什么呢？
//2.1 什么是⼤⼩端？
//其实超过⼀个字节的数据在内存中存储的时候，就有存储顺序的问题，按照不同的存储顺序，我们分
//为⼤端字节序存储和⼩端字节序存储，下⾯是具体的概念：
//⼤端（存储）模式：
//是指数据的低位字节内容保存在内存的⾼地址处，⽽数据的⾼位字节内容，保存在内存的低地址处。
//⼩端（存储）模式：
//是指数据的低位字节内容保存在内存的低地址处，⽽数据的⾼位字节内容，保存在内存的⾼地址处。
//上述概念需要记住，⽅便分辨⼤⼩端。
//2.2 为什么有⼤⼩端 ? 
//为什么会有⼤⼩端模式之分呢？
//这是因为在计算机系统中，我们是以字节为单位的，每个地址单元都对应着⼀个字节，⼀个字节为8
//bit位，但是在C语⾔中除了8bit的 char 之外，还有16bit的 short 型，32bit的 long 型（要看
//具体的编译器），另外，对于位数⼤于8位的处理器，例如16位或者32位的处理器，由于寄存器宽度⼤
//于⼀个字节，那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了⼤端存储模式和⼩端存
//储模式。
//例如：⼀个 16bit 的 short 型 x ，在内存中的地址为 0x0010 ， x 的值为 0x1122 ，那么
//0x11 为⾼字节， 0x22 为低字节。对于⼤端模式，就将 0x11 放在低地址中，即 0x0010 中，
//0x22 放在⾼地址中，即 0x0011 中。⼩端模式，刚好相反。我们常⽤的 X86 结构是⼩端模式，⽽
//KEIL C51 则为⼤端模式。很多的ARM，DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是
//⼤端模式还是⼩端模式。
//2.3 练习
//2.3.1 练习1
//请简述⼤端字节序和⼩端字节序的概念，设计⼀个⼩程序来判断当前机器的字节序。（10分） - 百度笔
//试题1
// 1 的16进制表示:  0x01。
// 00000000 00000000 00000000 00000001
//大端字节序：低位放在高地址处，高位放在低地址处  应该是1在高地址处，0在低地址处
//小端字节序：低位放在低地址处，高位放在高地址处  应该是0在高地址处，1在低地址处
//#include <stdio.h>
//int check_sks()
//{
//	int i = 1;
//	return *(char*)&i;
//}
//int main()
//{
//	int ret = check_sks();
//	if (ret == 1)
//	{
//		printf("机器是小端");
//	}
//	else
//		printf("机器是大端");
//	return 0;
//}
//#include <stdio.h>
//int check_sks()
//{
//	union
//	{
//		int i;
//		char c;
//	}un;
//	un.i = 1;
//	return un.c;
//}
////联合的特性：联合的所有成员共享同一块内存空间，因此 un.i 和 un.c 的起始地址相同。
//int main()
//{
//	int ret = check_sks();
//	if (ret == 1)
//	{
//		printf("机器是小端");
//	}
//	else
//		printf("机器是大端");
//	return 0;
//}
////输出接过：机器是小端
//#include <stdio.h>
//int main()
//{
//	char a = -1;
//	signed char b = -1;
//	unsigned char c = -1;
//	printf("a = %d, b = %d, c = %d", a, b, c);
//	return 0;
//}
// . 核心规则
/*小*整数类型（宽度 < int） 会被提升为 int 或 unsigned int：
	如果原类型能完整表示所有值，则提升为 int（常见情况）。
	如果原类型无法表示所有值（如 unsigned short 在 16 位系统中），则提升为 unsigned int。
	提升方式：
	有符号类型：符号扩展（Sign Extension）
	高位填充符号位（最高位），保持数值不变。
	无符号类型：零扩展（Zero Extension）
	高位直接补零，数值按无符号解释。*/
//////当前机器小段储存
////分析：-1的原码：1000 0000 0000 0000 0000 0000 0000 0001
////     -1的反码：1111 1111 1111 1111 1111 1111 1111 1110 //原码符号位不变其他位按位取反
////	 -1的补码：  1111 1111 1111 1111 1111 1111 1111 1111 //反码+1
////	  a为char类型 一个字节，8个比特位，赋值时发生截断，为 1111 1111 
//       用%d进行打印时，a发生整形提升,b同理
//                补码：1111 1111 1111 1111 1111 1111 1111 1111
//                原码：1000 0000 0000 0000 0000 0000 0000 0001 结果为-1
// 
//           用%d进行打印时，c发生整形提升,c是无符号的  高位直接补零，数值按无符号解释                                       
//                补码： 0000 0000 0000 0000 0000 0000 1111 1111
//                c是无符号的，原，反，补码相同结果为255
//#include <stdio.h>
//int main()
//{
//	char a = -128;
//	printf("%u\n", a);
//	return 0;
//}
//输出结果：4294967168
//%d:是以10进制的形式打印的有符号的整数---内存中存储的是有符号的补码
//%u:是以10进制的形式打印的无符号的整数---内存中存储的是无符号的补码
//#include <stdio.h>
//int main()
//{
//	char a = 128;
//	printf("%u\n", a);
//	return 0;
//}
//输出结果 4294967168
//#include <stdio.h>
//#include <string.h>
//int main()
//{
//	char a[1000];
//	int i;
//	for (i = 0; i < 1000; i++)
//	{
//		a[i] = -1 - i;
//	}
//	printf("%d", strlen(a));
//	return 0;
//}
//输出结果：255
//类型	符号性	取值范围	数值范围（十进制）
//signed char	有符号 - 128 ~127	包含负数、零和正数
//unsigned char	无符号	0 ~255	仅包含零和正数
//#include <stdio.h>
//unsigned char i = 0;
//int main()
//{
//	for (i = 0; i <= 255; i++)
//	{
//		printf("hello world\n");
//	}
//	return 0;
//}
//输出结果：死循环打印hello world
//#include <stdio.h>
//int main()
//{
//	unsigned int i;
//	for (i = 9; i >= 0; i--)
//	{
//		printf("%u\n", i);
//	}
//	return 0;
//}
//输出解果：死循环打印数字
//3. 浮点数在内存中的存储
//常⻅的浮点数：3.14159、1E10等，浮点数家族包括： float、double、long double 类型。
//浮点数表⽰的范围： float.h 中定义。
#/*include <stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为：%d\n", n);
	printf("*pFloat的值为：%f\n", *pFloat);
	*pFloat = 9.0;
	printf("n的值为：%d\n", n);
	printf("*pFloat的值为：%f\n", *pFloat);
	return 0;
}
输出结果：
n的值为：9
* pFloat的值为：0.000000
n的值为：1091567616
* pFloat的值为：9.000000
3.2 浮点数的存储
上⾯的代码中， n 和 * pFloat 在内存中明明是同⼀个数，为什么浮点数和整数的解读结果会差别这
么⼤？
要理解这个结果，⼀定要搞懂浮点数在计算机内部的表⽰⽅法。
根据国际标准IEEE（电⽓和电⼦⼯程协会）754，任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式：
V = (−1)∗s M ∗ 2
E
• (−1)表⽰符号位，当S = 0，V为正数；当S = 1，V为负数
S
• M表⽰有效数字，M是⼤于等于1，⼩于2的
• 2 表⽰指数位
E
举例来说：
⼗进制的5.0，写成⼆进制是 101.0 ，相当于1.01 * 2 。
2
那么，按照上⾯V的格式，可以得出S = 0，M = 1.01，E = 2。
⼗进制的 - 5.0，写成⼆进制是 - 101.0 ，相当于 - 1.01 * 2 ；那么，S = 1，M = 1.01，E = 2。
IEEE754规定：
对于32位的浮点数(float)，最⾼的1位存储符号位S，接着的8位存储指数E，剩下的23位存储有效数字
M
对于64位的浮点数(double)，最⾼的1位存储符号位S，接着的11位存储指数E，剩下的52位存储有效
数字M
3.2.1浮点数存的过程IEEE754对有效数字M和指数E，还有⼀些特别规定。
前⾯说过， 1 ≤ M < 2 ，也就是说，M可以写成 1.xxxxxx 的形式，其中 xxxxxx 表⽰⼩数部分。
IEEE754规定，在计算机内部保存M时，默认这个数的第⼀位总是1，因此可以被舍去，只保存后⾯的
xxxxxx部分。⽐如保存1.01的时候，只保存01，等到读取的时候，再把第⼀位的1加上去。这样做的⽬
的，是节省1位有效数字。以32位浮点数为例，留给M只有23位，将第⼀位的1舍去以后，等于可以保
存24位有效数字。
⾸先，E为⼀个⽆符号整数（unsignedint）
这意味着，如果E为8位，它的取值范围为0~255；如果E为11位，它的取值范围为0~2047。但是，我
们知道，科学计数法中的E是可以出现负数的，所以IEEE754规定，存⼊内存时E的真实值必须再加上
⼀个中间数，对于8位的E，这个中间数是127；对于11位的E，这个中间数是1023。
⽐如，2 的E是10，所以保存成32位浮点数时，必须保存成10 + 127 = 137，即10001001。
10
这样的浮点数存储⽅式很巧妙，但是我们也要注意到有的浮点数是⽆法精确保存的。⽐如:1.2，我们可
以在VS上调试看⼀下，我们发现会有些许误差。*/
int main()
{
	float a = 1.2;
	return 0;
}
调试看到的结果:a	1.20000005	float
3.2.2 浮点数取的过程
指数E从内存中取出还可以再分成三种情况：
E不全为0或不全为1（常规情况）
这时，浮点数就采⽤下⾯的规则表⽰，即指数E的计算值减去127（或1023），得到真实值，再将有效
数字M前加上第⼀位的1。
⽐如：0.5的⼆进制形式为0.1，由于规定正数部分必须为1，即将⼩数点右移1位，则为1.0 *  ，其
阶码为 - 1 + 127(中间值) = 126，表⽰为01111110，⽽尾数1.0去掉整数部分为0，补⻬0到23位
00000000000000000000000，则其⼆进制表⽰形式为:
0 01111110 00000000000000000000000

E全为0
这时，浮点数的指数E等于1 - 127（或者1 - 1023）即为真实值，有效数字M不再加上第⼀位的1，⽽是还
原为0.xxxxxx的⼩数。这样做是为了表⽰±0，以及接近于0的很⼩的数字。
0 00000000 00100000000000000000000
E全为1这时，如果有效数字M全为0，表⽰±⽆穷⼤（正负取决于符号位s）；
0 11111111 00010000000000000000000
